 /*******************************************************************************
  * Copyright (c) 2000, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.ui.dialogs;

 import java.util.ArrayList ;
 import java.util.Hashtable ;
 import java.util.Iterator ;
 import java.util.List ;
 import java.util.Map ;
 import java.util.Vector ;

 import org.eclipse.core.resources.IContainer;
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.resources.IResource;
 import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.jface.dialogs.IDialogConstants;
 import org.eclipse.jface.viewers.CheckStateChangedEvent;
 import org.eclipse.jface.viewers.ICheckStateListener;
 import org.eclipse.jface.viewers.IStructuredSelection;
 import org.eclipse.jface.viewers.ITreeContentProvider;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.custom.BusyIndicator;
 import org.eclipse.swt.events.SelectionAdapter;
 import org.eclipse.swt.events.SelectionEvent;
 import org.eclipse.swt.events.SelectionListener;
 import org.eclipse.swt.graphics.Font;
 import org.eclipse.swt.layout.GridData;
 import org.eclipse.swt.layout.GridLayout;
 import org.eclipse.swt.widgets.Button;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Shell;
 import org.eclipse.ui.ide.IDE;
 import org.eclipse.ui.internal.ide.DialogUtil;
 import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
 import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
 import org.eclipse.ui.internal.ide.dialogs.ResourceTreeAndListGroup;
 import org.eclipse.ui.model.WorkbenchContentProvider;
 import org.eclipse.ui.model.WorkbenchLabelProvider;

 /**
  * Abstract superclass for a typical export wizard's main page.
  * <p>
  * Clients may subclass this page to inherit its common destination resource
  * selection facilities.
  * </p>
  * <p>
  * Subclasses must implement
  * <ul>
  * <li><code>createDestinationGroup</code></li>
  * </ul>
  * </p>
  * <p>
  * Subclasses may override
  * <ul>
  * <li><code>allowNewContainerName</code></li>
  * </ul>
  * </p>
  * <p>
  * Subclasses may extend
  * <ul>
  * <li><code>handleEvent</code></li>
  * <li><code>internalSaveWidgetValues</code></li>
  * <li><code>updateWidgetEnablements</code></li>
  * </ul>
  * </p>
  */
 public abstract class WizardExportResourcesPage extends WizardDataTransferPage {
     private IStructuredSelection initialResourceSelection;

     private List selectedTypes = new ArrayList ();

     // widgets
 private ResourceTreeAndListGroup resourceGroup;

     private final static String SELECT_TYPES_TITLE = IDEWorkbenchMessages.WizardTransferPage_selectTypes;

     private final static String SELECT_ALL_TITLE = IDEWorkbenchMessages.WizardTransferPage_selectAll;

     private final static String DESELECT_ALL_TITLE = IDEWorkbenchMessages.WizardTransferPage_deselectAll;

     /**
      * Creates an export wizard page. If the current resource selection
      * is not empty then it will be used as the initial collection of resources
      * selected for export.
      *
      * @param pageName the name of the page
      * @param selection {@link IStructuredSelection} of {@link IResource}
      * @see IDE#computeSelectedResources(IStructuredSelection)
      */
     protected WizardExportResourcesPage(String pageName,
             IStructuredSelection selection) {
         super(pageName);
         this.initialResourceSelection = selection;
     }

     /**
      * The <code>addToHierarchyToCheckedStore</code> implementation of this
      * <code>WizardDataTransferPage</code> method returns <code>false</code>.
      * Subclasses may override this method.
      */
     protected boolean allowNewContainerName() {
         return false;
     }

     /**
      * Creates a new button with the given id.
      * <p>
      * The <code>Dialog</code> implementation of this framework method
      * creates a standard push button, registers for selection events
      * including button presses and registers
      * default buttons with its shell.
      * The button id is stored as the buttons client data.
      * Note that the parent's layout is assumed to be a GridLayout and
      * the number of columns in this layout is incremented.
      * Subclasses may override.
      * </p>
      *
      * @param parent the parent composite
      * @param id the id of the button (see
      * <code>IDialogConstants.*_ID</code> constants
      * for standard dialog button ids)
      * @param label the label from the button
      * @param defaultButton <code>true</code> if the button is to be the
      * default button, and <code>false</code> otherwise
      */
     protected Button createButton(Composite parent, int id, String label,
             boolean defaultButton) {
         // increment the number of columns in the button bar
 ((GridLayout) parent.getLayout()).numColumns++;

         Button button = new Button(parent, SWT.PUSH);

         GridData buttonData = new GridData(GridData.FILL_HORIZONTAL);
         button.setLayoutData(buttonData);

         button.setData(new Integer (id));
         button.setText(label);
         button.setFont(parent.getFont());

         if (defaultButton) {
             Shell shell = parent.getShell();
             if (shell != null) {
                 shell.setDefaultButton(button);
             }
             button.setFocus();
         }
         button.setFont(parent.getFont());
         setButtonLayoutData(button);
         return button;
     }

     /**
      * Creates the buttons for selecting specific types or selecting all or none of the
      * elements.
      *
      * @param parent the parent control
      */
     protected final void createButtonsGroup(Composite parent) {

         Font font = parent.getFont();

         // top level group
 Composite buttonComposite = new Composite(parent, SWT.NONE);
         buttonComposite.setFont(parent.getFont());

         GridLayout layout = new GridLayout();
         layout.numColumns = 3;
         layout.makeColumnsEqualWidth = true;
         buttonComposite.setLayout(layout);
         buttonComposite.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_FILL
                 | GridData.HORIZONTAL_ALIGN_FILL));

         // types edit button
 Button selectTypesButton = createButton(buttonComposite,
                 IDialogConstants.SELECT_TYPES_ID, SELECT_TYPES_TITLE, false);

         SelectionListener listener = new SelectionAdapter() {
             public void widgetSelected(SelectionEvent e) {
                 handleTypesEditButtonPressed();
             }
         };
         selectTypesButton.addSelectionListener(listener);
         selectTypesButton.setFont(font);
         setButtonLayoutData(selectTypesButton);

         Button selectButton = createButton(buttonComposite,
                 IDialogConstants.SELECT_ALL_ID, SELECT_ALL_TITLE, false);

         listener = new SelectionAdapter() {
             public void widgetSelected(SelectionEvent e) {
                 resourceGroup.setAllSelections(true);
             }
         };
         selectButton.addSelectionListener(listener);
         selectButton.setFont(font);
         setButtonLayoutData(selectButton);

         Button deselectButton = createButton(buttonComposite,
                 IDialogConstants.DESELECT_ALL_ID, DESELECT_ALL_TITLE, false);

         listener = new SelectionAdapter() {
             public void widgetSelected(SelectionEvent e) {
                 resourceGroup.setAllSelections(false);
             }
         };
         deselectButton.addSelectionListener(listener);
         deselectButton.setFont(font);
         setButtonLayoutData(deselectButton);

     }

     /** (non-Javadoc)
      * Method declared on IDialogPage.
      */
     public void createControl(Composite parent) {

         initializeDialogUnits(parent);

         Composite composite = new Composite(parent, SWT.NULL);
         composite.setLayout(new GridLayout());
         composite.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_FILL
                 | GridData.HORIZONTAL_ALIGN_FILL));
         composite.setFont(parent.getFont());

         createResourcesGroup(composite);
         createButtonsGroup(composite);

         createDestinationGroup(composite);

         createOptionsGroup(composite);

         restoreResourceSpecificationWidgetValues(); // ie.- local
 restoreWidgetValues(); // ie.- subclass hook
 if (initialResourceSelection != null) {
             setupBasedOnInitialSelections();
         }

         updateWidgetEnablements();
         setPageComplete(determinePageCompletion());
         setErrorMessage(null); // should not initially have error message

         setControl(composite);
     }

     /**
      * Creates the export destination specification visual components.
      * <p>
      * Subclasses must implement this method.
      * </p>
      *
      * @param parent the parent control
      */
     protected abstract void createDestinationGroup(Composite parent);

     /**
      * Creates the checkbox tree and list for selecting resources.
      *
      * @param parent the parent control
      */
     protected final void createResourcesGroup(Composite parent) {

         //create the input element, which has the root resource
 //as its only child
 List input = new ArrayList ();
         IProject[] projects = ResourcesPlugin.getWorkspace().getRoot()
                 .getProjects();
         for (int i = 0; i < projects.length; i++) {
             if (projects[i].isOpen()) {
                 input.add(projects[i]);
             }
         }

         this.resourceGroup = new ResourceTreeAndListGroup(parent, input,
                 getResourceProvider(IResource.FOLDER | IResource.PROJECT),
                 WorkbenchLabelProvider.getDecoratingWorkbenchLabelProvider(),
                 getResourceProvider(IResource.FILE), WorkbenchLabelProvider
                         .getDecoratingWorkbenchLabelProvider(), SWT.NONE,
                 DialogUtil.inRegularFontMode(parent));
         
         ICheckStateListener listener = new ICheckStateListener() {
             public void checkStateChanged(CheckStateChangedEvent event) {
                 updateWidgetEnablements();
             }
         };
         
         this.resourceGroup.addCheckStateListener(listener);
     }

     /*
      * @see WizardDataTransferPage.getErrorDialogTitle()
      */
     protected String getErrorDialogTitle() {
         return IDEWorkbenchMessages.WizardExportPage_errorDialogTitle;
     }

     /**
      * Obsolete method. This was implemented to handle the case where ensureLocal()
      * needed to be called but it doesn't use it any longer.
      *
      * @deprecated Only retained for backwards compatibility.
      */
     protected boolean ensureResourcesLocal(List resources) {
         return true;
     }

     /**
      * Returns a new subcollection containing only those resources which are not
      * local.
      *
      * @param originalList the original list of resources (element type:
      * <code>IResource</code>)
      * @return the new list of non-local resources (element type:
      * <code>IResource</code>)
      */
     protected List extractNonLocalResources(List originalList) {
         Vector result = new Vector (originalList.size());
         Iterator resourcesEnum = originalList.iterator();

         while (resourcesEnum.hasNext()) {
             IResource currentResource = (IResource) resourcesEnum.next();
             if (!currentResource.isLocal(IResource.DEPTH_ZERO)) {
                 result.addElement(currentResource);
             }
         }

         return result;
     }

     /**
      * Returns a content provider for <code>IResource</code>s that returns
      * only children of the given resource type.
      */
     private ITreeContentProvider getResourceProvider(final int resourceType) {
         return new WorkbenchContentProvider() {
             public Object [] getChildren(Object o) {
                 if (o instanceof IContainer) {
                     IResource[] members = null;
                     try {
                         members = ((IContainer) o).members();
                     } catch (CoreException e) {
                         //just return an empty set of children
 return new Object [0];
                     }

                     //filter out the desired resource types
 ArrayList results = new ArrayList ();
                     for (int i = 0; i < members.length; i++) {
                         //And the test bits with the resource types to see if they are what we want
 if ((members[i].getType() & resourceType) > 0) {
                             results.add(members[i]);
                         }
                     }
                     return results.toArray();
                 }
                 //input element case
 if (o instanceof ArrayList ) {
                     return ((ArrayList ) o).toArray();
                 }
                 return new Object [0];
             }
         };
     }

     /**
      * Returns this page's collection of currently-specified resources to be
      * exported. This is the primary resource selection facility accessor for
      * subclasses.
      *
      * @return a collection of resources currently selected
      * for export (element type: <code>IResource</code>)
      */
     protected List getSelectedResources() {
         Iterator resourcesToExportIterator = this
                 .getSelectedResourcesIterator();
         List resourcesToExport = new ArrayList ();
         while (resourcesToExportIterator.hasNext()) {
             resourcesToExport.add(resourcesToExportIterator.next());
         }
         return resourcesToExport;
     }

     /**
      * Returns this page's collection of currently-specified resources to be
      * exported. This is the primary resource selection facility accessor for
      * subclasses.
      *
      * @return an iterator over the collection of resources currently selected
      * for export (element type: <code>IResource</code>). This will include
      * white checked folders and individually checked files.
      */
     protected Iterator getSelectedResourcesIterator() {
         return this.resourceGroup.getAllCheckedListItems().iterator();
     }

     /**
      * Returns the resource extensions currently specified to be exported.
      *
      * @return the resource extensions currently specified to be exported (element
      * type: <code>String</code>)
      */
     protected List getTypesToExport() {

         return selectedTypes;
     }

     /**
      * Returns this page's collection of currently-specified resources to be
      * exported. This returns both folders and files - for just the files use
      * getSelectedResources.
      *
      * @return a collection of resources currently selected
      * for export (element type: <code>IResource</code>)
      */
     protected List getWhiteCheckedResources() {

         return this.resourceGroup.getAllWhiteCheckedItems();
     }

     /**
      * Queries the user for the types of resources to be exported and selects
      * them in the checkbox group.
      */
     protected void handleTypesEditButtonPressed() {
         Object [] newSelectedTypes = queryResourceTypesToExport();

         if (newSelectedTypes != null) { // ie.- did not press Cancel
 this.selectedTypes = new ArrayList (newSelectedTypes.length);
             for (int i = 0; i < newSelectedTypes.length; i++) {
                 this.selectedTypes.add(newSelectedTypes[i]);
             }
             setupSelectionsBasedOnSelectedTypes();
         }

     }

     /**
      * Returns whether the extension of the given resource name is an extension that
      * has been specified for export by the user.
      *
      * @param resourceName the resource name
      * @return <code>true</code> if the resource name is suitable for export based
      * upon its extension
      */
     protected boolean hasExportableExtension(String resourceName) {
         if (selectedTypes == null) {
             return true;
         }

         int separatorIndex = resourceName.lastIndexOf("."); //$NON-NLS-1$
 if (separatorIndex == -1) {
             return false;
         }

         String extension = resourceName.substring(separatorIndex + 1);

         Iterator it = selectedTypes.iterator();
         while (it.hasNext()) {
             if (extension.equalsIgnoreCase((String ) it.next())) {
                 return true;
             }
         }

         return false;
     }

     /**
      * Persists additional setting that are to be restored in the next instance of
      * this page.
      * <p>
      * The <code>WizardImportPage</code> implementation of this method does
      * nothing. Subclasses may extend to persist additional settings.
      * </p>
      */
     protected void internalSaveWidgetValues() {
     }

     /**
      * Queries the user for the resource types that are to be exported and returns
      * these types as an array.
      *
      * @return the resource types selected for export (element type:
      * <code>String</code>), or <code>null</code> if the user canceled the
      * selection
      */
     protected Object [] queryResourceTypesToExport() {

         TypeFilteringDialog dialog = new TypeFilteringDialog(getContainer()
                 .getShell(), getTypesToExport());

         dialog.open();

         return dialog.getResult();
     }

     /**
      * Restores resource specification control settings that were persisted
      * in the previous instance of this page. Subclasses wishing to restore
      * persisted values for their controls may extend.
      */
     protected void restoreResourceSpecificationWidgetValues() {
     }

     /**
      * Persists resource specification control setting that are to be restored
      * in the next instance of this page. Subclasses wishing to persist additional
      * setting for their controls should extend hook method
      * <code>internalSaveWidgetValues</code>.
      */
     protected void saveWidgetValues() {

         // allow subclasses to save values
 internalSaveWidgetValues();

     }

     /**
      * Set the initial selections in the resource group.
      */
     protected void setupBasedOnInitialSelections() {

         Iterator it = this.initialResourceSelection.iterator();
         while (it.hasNext()) {
             IResource currentResource = (IResource) it.next();
             if (currentResource.getType() == IResource.FILE) {
                 this.resourceGroup.initialCheckListItem(currentResource);
             } else {
                 this.resourceGroup.initialCheckTreeItem(currentResource);
             }
         }
     }

     /**
      * Update the tree to only select those elements that match the selected types
      */
     private void setupSelectionsBasedOnSelectedTypes() {

         Runnable runnable = new Runnable () {
             public void run() {
                 Map selectionMap = new Hashtable ();
                 //Only get the white selected ones
 Iterator resourceIterator = resourceGroup
                         .getAllWhiteCheckedItems().iterator();
                 while (resourceIterator.hasNext()) {
                     //handle the files here - white checked containers require recursion
 IResource resource = (IResource) resourceIterator.next();
                     if (resource.getType() == IResource.FILE) {
                         if (hasExportableExtension(resource.getName())) {
                             List resourceList = new ArrayList ();
                             IContainer parent = resource.getParent();
                             if (selectionMap.containsKey(parent)) {
                                 resourceList = (List ) selectionMap.get(parent);
                             }
                             resourceList.add(resource);
                             selectionMap.put(parent, resourceList);
                         }
                     } else {
                         setupSelectionsBasedOnSelectedTypes(selectionMap,
                                 (IContainer) resource);
                     }
                 }
                 resourceGroup.updateSelections(selectionMap);
             }
         };

         BusyIndicator.showWhile(getShell().getDisplay(), runnable);

     }

     /**
      * Set up the selection values for the resources and put them in the selectionMap.
      * If a resource is a file see if it matches one of the selected extensions. If not
      * then check the children.
      */
     private void setupSelectionsBasedOnSelectedTypes(Map selectionMap,
             IContainer parent) {

         List selections = new ArrayList ();
         IResource[] resources;
         boolean hasFiles = false;

         try {
             resources = parent.members();
         } catch (CoreException exception) {
             //Just return if we can't get any info
 return;
         }

         for (int i = 0; i < resources.length; i++) {
             IResource resource = resources[i];
             if (resource.getType() == IResource.FILE) {
                 if (hasExportableExtension(resource.getName())) {
                     hasFiles = true;
                     selections.add(resource);
                 }
             } else {
                 setupSelectionsBasedOnSelectedTypes(selectionMap,
                         (IContainer) resource);
             }
         }

         //Only add it to the list if there are files in this folder
 if (hasFiles) {
             selectionMap.put(parent, selections);
         }
     }

     /**
      * Save any editors that the user wants to save before export.
      * @return boolean if the save was successful.
      */
     protected boolean saveDirtyEditors() {
         return IDEWorkbenchPlugin.getDefault().getWorkbench().saveAllEditors(
                 true);
     }
     
     /**
      * Check if widgets are enabled or disabled by a change in the dialog.
      */
     protected void updateWidgetEnablements() {

         boolean pageComplete = determinePageCompletion();
         setPageComplete(pageComplete);
         if (pageComplete) {
             setMessage(null);
         }
         super.updateWidgetEnablements();
     }
 }

